#include "PlayerManager.h"
#include "ResourcesManager.h"
#include "ConfigManager.h"
#include "EnemyManager.h"
#include "CoinManager.h"

PlayerManager::PlayerManager()
{
    timerAutoIncreaseMp.SetOneShot(false);
    timerAutoIncreaseMp.SetWaitTime(0.1);
    timerAutoIncreaseMp.SetOnTimeout([&]() {
        double interval = ConfigManager::Instance().player_template.skill_interval;
        mp = std::min(mp + 100 / (interval / 0.1), 100.0);
    });

    timerReleaseFlashCd.SetOneShot(true);
    timerReleaseFlashCd.SetWaitTime(ConfigManager::Instance().player_template.skill_interval);
    timerReleaseFlashCd.SetOnTimeout([&]() { canReleaseFlash = true; });

    const ResourcesManager::TexturePool &tex_pool = ResourcesManager::Instance().texturePool;
    SDL_Texture *tex_player = tex_pool.find(ResID::Tex_Player)->second;

    anim_idle_up.SetLoop(true);
    anim_idle_up.SetInterval(0.1);
    anim_idle_up.SetFrameData(tex_player, 4, 8, {4, 5, 6, 7});
    anim_idle_down.SetLoop(true);
    anim_idle_down.SetInterval(0.1);
    anim_idle_down.SetFrameData(tex_player, 4, 8, {0, 1, 2, 3});
    anim_idle_left.SetLoop(true);
    anim_idle_left.SetInterval(0.1);
    anim_idle_left.SetFrameData(tex_player, 4, 8, {8, 9, 10, 11});
    animIdleRight.SetLoop(true);
    animIdleRight.SetInterval(0.1);
    animIdleRight.SetFrameData(tex_player, 4, 8, {12, 13, 14, 15});

    animAttackUp.SetLoop(true);
    animAttackUp.SetInterval(0.1);
    animAttackUp.SetFrameData(tex_player, 4, 8, {20, 21});
    animAttackDown.SetLoop(true);
    animAttackDown.SetInterval(0.1);
    animAttackDown.SetFrameData(tex_player, 4, 8, {16, 17});
    animAttackLeft.SetLoop(true);
    animAttackLeft.SetInterval(0.1);
    animAttackLeft.SetFrameData(tex_player, 4, 8, {24, 25});
    animAttackRight.SetLoop(true);
    animAttackRight.SetInterval(0.1);
    animAttackRight.SetFrameData(tex_player, 4, 8, {28, 29});

    animEffectFlashUp.SetLoop(false);
    animEffectFlashUp.SetInterval(0.1);
    animEffectFlashUp.SetFrameData(tex_pool.find(ResID::Tex_EffectFlash_Up)->second, 5, 1, {0, 1, 2, 3, 4});
    animEffectFlashUp.SetOnFinished([&]() { isReleasingFlash = false; });
    animEffectFlashDown.SetLoop(false);
    animEffectFlashDown.SetInterval(0.1);
    animEffectFlashDown.SetFrameData(tex_pool.find(ResID::Tex_EffectFlash_Down)->second, 5, 1, {4, 3, 2, 1, 0});
    animEffectFlashDown.SetOnFinished([&]() { isReleasingFlash = false; });
    animEffectFlashLeft.SetLoop(false);
    animEffectFlashLeft.SetInterval(0.1);
    animEffectFlashLeft.SetFrameData(tex_pool.find(ResID::Tex_EffectFlash_Left)->second, 1, 5, {4, 3, 2, 1, 0});
    animEffectFlashLeft.SetOnFinished([&]() { isReleasingFlash = false; });
    animEffectFlashRight.SetLoop(false);
    animEffectFlashRight.SetInterval(0.1);
    animEffectFlashRight.SetFrameData(tex_pool.find(ResID::Tex_EffectFlash_Right)->second, 1, 5, {0, 1, 2, 3, 4});
    animEffectFlashRight.SetOnFinished([&]() { isReleasingFlash = false; });

    animEffectImpactUp.SetLoop(false);
    animEffectImpactUp.SetInterval(0.1);
    animEffectImpactUp.SetFrameData(tex_pool.find(ResID::Tex_EffectImpact_Up)->second, 5, 1, {0, 1, 2, 3, 4});
    animEffectImpactUp.SetOnFinished([&]() { isReleasingImpact = false; });
    animEffectImpactDown.SetLoop(false);
    animEffectImpactDown.SetInterval(0.1);
    animEffectImpactDown.SetFrameData(tex_pool.find(ResID::Tex_EffectImpact_Down)->second, 5, 1, {4, 3, 2, 1, 0});
    animEffectImpactDown.SetOnFinished([&]() { isReleasingImpact = false; });
    animEffectImpactLeft.SetLoop(false);
    animEffectImpactLeft.SetInterval(0.1);
    animEffectImpactLeft.SetFrameData(tex_pool.find(ResID::Tex_EffectImpact_Left)->second, 1, 5, {4, 3, 2, 1, 0});
    animEffectImpactLeft.SetOnFinished([&]() { isReleasingImpact = false; });
    animEffectImpactRight.SetLoop(false);
    animEffectImpactRight.SetInterval(0.1);
    animEffectImpactRight.SetFrameData(tex_pool.find(ResID::Tex_EffectImpact_Right)->second, 1, 5, {0, 1, 2, 3, 4});
    animEffectImpactRight.SetOnFinished([&]() { isReleasingImpact = false; });

    const SDL_Rect &rectMap = ConfigManager::Instance().rectTileMap;
    position.x = rectMap.x + rectMap.w / 2;
    position.y = rectMap.y + rectMap.h / 2;

    speed = ConfigManager::Instance().player_template.speed;

    size.x = 96, size.y = 96;
}

void PlayerManager::OnInput(const SDL_Event &event)
{
    switch (event.type) {
        case SDL_KEYDOWN:
            switch (event.key.keysym.sym) {
                case SDLK_a:
                    isMoveLeft = true;
                    break;
                case SDLK_d:
                    isMoveRight = true;
                    break;
                case SDLK_w:
                    isMoveUp = true;
                    break;
                case SDLK_s:
                    isMoveDown = true;
                    break;
                case SDLK_j:
                    OnReleaseFlash();
                    break;
                case SDLK_k:
                    OnReleaseImpact();
                    break;
                default:
                    break;
            }
            break;
        case SDL_KEYUP:
            switch (event.key.keysym.sym) {
                case SDLK_a:
                    isMoveLeft = false;
                    break;
                case SDLK_d:
                    isMoveRight = false;
                    break;
                case SDLK_w:
                    isMoveUp = false;
                    break;
                case SDLK_s:
                    isMoveDown = false;
                    break;
                default:
                    break;
            }
            break;
        default:
            break;
    }
}

void PlayerManager::OnUpdate(double delta)
{
    timerAutoIncreaseMp.OnUpdate(delta);
    timerReleaseFlashCd.OnUpdate(delta);

    Vector2 direction = Vector2(isMoveRight - isMoveLeft, isMoveDown - isMoveUp).Normalize();
    velocity = direction * speed * SIZE_TILE;

    if (!isReleasingFlash && !isReleasingImpact) {
        position += velocity * delta;

        const SDL_Rect &rectMap = ConfigManager::Instance().rectTileMap;
        if (position.x < rectMap.x) position.x = rectMap.x;
        if (position.x > rectMap.x + rectMap.w) position.x = rectMap.x + rectMap.w;
        if (position.y < rectMap.y) position.y = rectMap.y;
        if (position.y > rectMap.y + rectMap.h) position.y = rectMap.y + rectMap.h;

        if (velocity.y > 0) facing = Facing::Down;
        if (velocity.y < 0) facing = Facing::Up;
        if (velocity.x > 0) facing = Facing::Right;
        if (velocity.x < 0) facing = Facing::Left;

        switch (facing) {
            case Facing::Left:
                animCurrent = &anim_idle_left;
                break;
            case Facing::Right:
                animCurrent = &animIdleRight;
                break;
            case Facing::Up:
                animCurrent = &anim_idle_up;
                break;
            case Facing::Down:
                animCurrent = &anim_idle_down;
                break;
        }
    } else {
        switch (facing) {
            case Facing::Left:
                animCurrent = &animAttackLeft;
                break;
            case Facing::Right:
                animCurrent = &animAttackRight;
                break;
            case Facing::Up:
                animCurrent = &animAttackUp;
                break;
            case Facing::Down:
                animCurrent = &animAttackDown;
                break;
        }
    }

    animCurrent->OnUpdate(delta);
    if (isReleasingFlash) {
        animEffectFlashCurrent->OnUpdate(delta);

        std::vector<Enemy*> &enemyList = EnemyManager::Instance().GetEnemyList();
        for (Enemy *enemy: enemyList) {
            if (enemy->CanRemove())
                continue;

            const Vector2 &position = enemy->GetPosition();
            if (position.x >= rectHitboxFlash.x
                && position.x <= rectHitboxFlash.x + rectHitboxFlash.w
                && position.y >= rectHitboxFlash.y
                && position.y <= rectHitboxFlash.y + rectHitboxFlash.h) {
                enemy->DecreaseHp(ConfigManager::Instance().player_template.normal_attack_damage * delta);
            }
        }
    }

    if (isReleasingImpact) {
        animEffectImpactCurrent->OnUpdate(delta);

        std::vector<Enemy*> &enemy_list = EnemyManager::Instance().GetEnemyList();
        for (Enemy *enemy: enemy_list) {
            if (enemy->CanRemove())
                continue;

            const Vector2 &size = enemy->GetSize();
            const Vector2 &position = enemy->GetPosition();
            if (position.x >= rectHitboxImpact.x
                && position.x <= rectHitboxImpact.x + rectHitboxImpact.w
                && position.y >= rectHitboxImpact.y
                && position.y <= rectHitboxImpact.y + rectHitboxImpact.h) {
                enemy->DecreaseHp(ConfigManager::Instance().player_template.skill_damage * delta);
                enemy->SlowDown();
            }
        }
    }

    std::vector<CoinProp*> &coin_prop_list = CoinManager::Instance().GetCoinPropList();
    static const ResourcesManager::SoundPool &sound_pool = ResourcesManager::Instance().soundPool;

    for (CoinProp *coinProp: coin_prop_list) {
        if (coinProp->CanRemove())
            continue;

        const Vector2 &posCoinProp = coinProp->GetPosition();
        if (posCoinProp.x >= position.x - size.x / 2
            && posCoinProp.x <= position.x + size.x / 2
            && posCoinProp.y >= position.y - size.y / 2
            && posCoinProp.y <= position.y + size.y / 2) {
            coinProp->MakeInvalid();
            CoinManager::Instance().IncreaseCoin(10);
            Mix_PlayChannel(-1, sound_pool.find(ResID::Sound_Coin)->second, 0);
        }
    }
}

void PlayerManager::OnRender(SDL_Renderer *renderer)
{
    static SDL_Point point;

    point.x = (int) (position.x - size.x / 2);
    point.y = (int) (position.y - size.y / 2);
    animCurrent->OnRender(renderer, point);

    if (isReleasingFlash) {
        point.x = rectHitboxFlash.x;
        point.y = rectHitboxFlash.y;
        animEffectFlashCurrent->OnRender(renderer, point);
    }

    if (isReleasingImpact) {
        point.x = rectHitboxImpact.x;
        point.y = rectHitboxImpact.y;
        animEffectImpactCurrent->OnRender(renderer, point);
    }
}

double PlayerManager::GetCurrentMp() const
{
    return mp;
}

void PlayerManager::OnReleaseFlash()
{
    if (!canReleaseFlash || isReleasingFlash)
        return;

    switch (facing) {
        case Facing::Left:
            animEffectFlashCurrent = &animEffectFlashLeft;
            rectHitboxFlash.x = (int) (position.x - size.x / 2 - 300);
            rectHitboxFlash.y = (int) (position.y - 68 / 2);
            rectHitboxFlash.w = 300, rectHitboxFlash.h = 68;
            break;
        case Facing::Right:
            animEffectFlashCurrent = &animEffectFlashRight;
            rectHitboxFlash.x = (int) (position.x + size.x / 2);
            rectHitboxFlash.y = (int) (position.y - 68 / 2);
            rectHitboxFlash.w = 300, rectHitboxFlash.h = 68;
            break;
        case Facing::Up:
            animEffectFlashCurrent = &animEffectFlashUp;
            rectHitboxFlash.x = (int) (position.x - 68 / 2);
            rectHitboxFlash.y = (int) (position.y - size.x / 2 - 300);
            rectHitboxFlash.w = 68, rectHitboxFlash.h = 300;
            break;
        case Facing::Down:
            animEffectFlashCurrent = &animEffectFlashDown;
            rectHitboxFlash.x = (int) (position.x - 68 / 2);
            rectHitboxFlash.y = (int) (position.y + size.x / 2);
            rectHitboxFlash.w = 68, rectHitboxFlash.h = 300;
            break;
    }

    isReleasingFlash = true;
    animEffectFlashCurrent->Reset();
    timerReleaseFlashCd.Restart();

    static const ResourcesManager::SoundPool &sound_pool= ResourcesManager::Instance().soundPool;
    Mix_PlayChannel(-1, sound_pool.find(ResID::Sound_Flash)->second, 0);
}

void PlayerManager::OnReleaseImpact()
{
    if (mp < 100 || isReleasingImpact)
        return;

    switch (facing) {
        case Facing::Left:
            animEffectImpactCurrent = &animEffectImpactLeft;
            rectHitboxImpact.x = (int) (position.x - size.x / 2 - 60);
            rectHitboxImpact.y = (int) (position.y - 140 / 2);
            rectHitboxImpact.w = 60, rectHitboxImpact.h = 140;
            break;
        case Facing::Right:
            animEffectImpactCurrent = &animEffectImpactRight;
            rectHitboxImpact.x = (int) (position.x + size.x / 2);
            rectHitboxImpact.y = (int) (position.y - 140 / 2);
            rectHitboxImpact.w = 60, rectHitboxImpact.h = 140;
            break;
        case Facing::Up:
            animEffectImpactCurrent = &animEffectImpactUp;
            rectHitboxImpact.x = (int) (position.x - 140 / 2);
            rectHitboxImpact.y = (int) (position.y - size.x / 2 - 60);
            rectHitboxImpact.w = 140, rectHitboxImpact.h = 60;
            break;
        case Facing::Down:
            animEffectImpactCurrent = &animEffectImpactDown;
            rectHitboxImpact.x = (int) (position.x - 140 / 2);
            rectHitboxImpact.y = (int) (position.y + size.x / 2);
            rectHitboxImpact.w = 140, rectHitboxImpact.h = 60;
            break;
    }

    mp = 0;
    isReleasingImpact = true;
    animEffectImpactCurrent->Reset();

    static const ResourcesManager::SoundPool &sound_pool = ResourcesManager::Instance().soundPool;
    Mix_PlayChannel(-1, sound_pool.find(ResID::Sound_Impact)->second, 0);
}